#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <ctype.h>
#include <dirent.h>
#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <algorithm>
#include <atomic>
#include <chrono>
#include <functional>
#include <string>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>

#include "ColorOutput.hpp"
#include "Share.hpp"

template <class T>
void Read(T* num = NULL, FILE* fp = stdin) {
  T    x = 0, f = 1;
  char ch = getc(fp);
  while (ch > '9' || ch < '0') {
    if (ch == '-') { f = -1; }
    ch = getc(fp);
  }
  while (ch >= '0' && ch <= '9') {
    x  = x * 10 + ch - '0';
    ch = getc(fp);
  }
  if (num) { *num = x * f; }
  return;
}

const int  COL             = 8;
const int  ROW             = 100005;
const int  MAX_DISPLAY_ROW = 20;
const char EMPTY           = '.';

int row, delay, displayRow, currentRow, bpm, bgmDelay, aCount, bCount, fCount,
    score, combo, maxCombo, fellCount, totalNotesCount, lastStatus[COL];
int         speed = 1;
bool        musicPlaying, gameRunning;
char        spectrum[COL][ROW], colToChar[COL];
std::string title, soundName;

void InitSDL() {
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
    fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
    exit(1);
  }

  int      audio_rate     = 22050;
  uint16_t audio_format   = AUDIO_S16SYS;
  int      audio_channels = 2;
  int      audio_buffers  = 4096;

  if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) !=
      0) {
    fprintf(stderr, "Unable to initialize audio: %s\n", Mix_GetError());
    exit(1);
  }
  return;
}

void PlayBGM(const std::string& fileName, int bgmDelay) {
  std::this_thread::sleep_for(std::chrono::milliseconds(bgmDelay));
  Mix_Music* music = Mix_LoadMUS(fileName.c_str());
  if (music == NULL) {
    printf("Unable to load Ogg file: %s\n", Mix_GetError());
    exit(1);
  }

  if (Mix_PlayMusic(music, 0) == -1) {
    printf("Unable to play Ogg file: %s\n", Mix_GetError());
    exit(1);
  }

  musicPlaying = true;
  Mix_HookMusicFinished([]() { musicPlaying = false; });

  while (musicPlaying) {}
  Mix_HaltMusic();
  Mix_FreeMusic(music);
  Mix_CloseAudio();

  return;
}

void LoadSpectrum(const std::string& fileName) {
  FILE* fp = fopen(("./Spectrums/" + fileName).c_str(), "r");
  Read(&row, fp);                 // 这张乐谱一共有几行
  Read(&bpm, fp);                 // 乐谱的 BPM
  Read(&bgmDelay, fp);            // BGM 延迟几秒响起
  delay      = 60.0 / bpm * 1000; // 根据 BPM 计算延迟
  displayRow = row <= MAX_DISPLAY_ROW ? row : MAX_DISPLAY_ROW;
  int randBuffer[COL];
  for (int i = 0; i < COL; ++i) {
    randBuffer[i] = i;
    do { colToChar[i] = std::toupper(getc(fp)); } while (isspace(colToChar[i]));
  }
  for (int j = 0; j < row * speed; ++j) {
    if (j % speed != 0) { continue; }
    int lineMode;
    Read(&lineMode, fp);
    if (lineMode == 0) {
      // 空行
      continue;
    } else if (lineMode == 1) {
      // 手动生成
      for (int i = 0; i < COL; ++i) {
        do {
          spectrum[i][j] = std::toupper(getc(fp));
        } while (isspace(spectrum[i][j]));
        if (spectrum[i][j] != EMPTY) { totalNotesCount++; }
      }
    } else if (lineMode == 2) {
      // 随机生成
      int lineNotesCount;
      Read(&lineNotesCount, fp); // 要生成的音符个数
      totalNotesCount += lineNotesCount;
      std::random_shuffle(randBuffer, randBuffer + COL);
      for (int i = 0; i < lineNotesCount; ++i) {
        spectrum[randBuffer[i]][j] = colToChar[randBuffer[i]];
      }
    } else if (lineMode == 3) {
      // 复制上一行
      // 1. 找到一个有音符的行
      int  k    = j;
      bool flag = true;
      while (flag) {
        k--;
        if (k < 0) { exit(EXIT_FAILURE); }
        for (int i = 0; i < COL; ++i) {
          if (spectrum[i][k] != EMPTY) {
            flag = false;
            break;
          }
        }
      }
      // 2. 复制
      for (int i = 0; i < COL; ++i) {
        if (spectrum[i][k] != EMPTY) {
          totalNotesCount++;
          spectrum[i][j] = spectrum[i][k];
        }
      }
    } else {
      fprintf(stderr, "Spectrum parse error on line%d\n", 3 + j / speed);
      exit(EXIT_FAILURE);
    }
  }
  fclose(fp);
  return;
}

void UpdateSpectrum() {
  currentRow++;
  return;
}

void CheckFell() {
  for (int i = 0; i < COL; ++i) {
    if (currentRow - 2 >= 0 && spectrum[i][currentRow - 2] != EMPTY) {
      fCount++;
      fellCount++;
      combo                       = 0;
      spectrum[i][currentRow - 2] = EMPTY;
      lastStatus[i]               = 3;
      if (fellCount == totalNotesCount) {
        musicPlaying = false;
        gameRunning  = false;
        return;
      }
    }
  }
  return;
}

void UpdateScreen() {
  Share::Clear();
  for (int j = displayRow - 1; j >= 0; --j) {
    for (int i = 0; i < COL; ++i) {
      printf(MAIN_BG(" "));
      if (i == COL / 2) { printf(MAIN_BG("     ")); }
      if (spectrum[i][j + currentRow] == EMPTY) {
        printf(RAIL_BG("     "));
      } else {
        printf(BLOCK_GREEN("[ %c ]"), spectrum[i][j + currentRow]);
      }
    }
    printf(MAIN_BG(" "));
    if (j == displayRow - 2) {
      printf("  %s", BOLDX(FBLUX(title)).c_str());
      printf(RST);
    } else if (j == displayRow - 8) {
      printf("  Combo %9d", combo);
    } else if (j == displayRow - 10) {
      printf("  Score %9d", score);
    } else if (j == displayRow - 19) {
      printf("  Press [ESC] to exit");
    }
    putchar('\n');
  }
  for (int i = 0; i < COL; ++i) {
    printf(MAIN_BG(" "));
    if (i == COL / 2) { printf(MAIN_BG("     ")); }
    if (spectrum[i][currentRow - 1] == EMPTY) {
      printf(RAIL_DARK_BG("     "));
    } else {
      printf(BLOCK_GREEN("[ %c ]"), spectrum[i][currentRow - 1]);
    }
  }
  printf(MAIN_BG(" ") "\n");
  for (int i = 0; i < COL; ++i) {
    printf(MAIN_BG(" "));
    if (i == COL / 2) { printf(MAIN_BG("     ")); }
    if (lastStatus[i]) {
      printf(
          "%s",
          lastStatus[i] == 1 ?
              MAIN_BG(PERFECT_TEXT("GOOD ")) :
              lastStatus[i] == 2 ? MAIN_BG(GOOD_TEXT("PREF ")) :
                                   MAIN_BG(MISS_TEXT("MISS ")));
      lastStatus[i] = 0;
    } else {
      printf(MAIN_BG("     "));
    }
  }
  printf(MAIN_BG(" ") "\n");
}

void CheckHit(char c) {
  for (const auto& item:
       { std::tuple<int, int*, int, int> { -2, &bCount, 1, 100 },
         { -1, &aCount, 2, 200 },
         { 0, &bCount, 1, 200 } }) {
    for (int i = 0; i < COL; ++i) {
      if (currentRow + std::get<0>(item) >= 0 &&
          currentRow + std::get<0>(item) < row * speed &&
          spectrum[i][currentRow + std::get<0>(item)] == c) {
        spectrum[i][currentRow + std::get<0>(item)] = EMPTY;
        (*std::get<1>(item))++;
        score += std::get<3>(item);
        lastStatus[i] = std::get<2>(item);
        fellCount++;
        if (fellCount == totalNotesCount) {
          musicPlaying = false;
          gameRunning  = false;
        }
        combo++;
        maxCombo = std::max(maxCombo, combo);
        return;
      }
    }
  }
  return;
}

void KeyboardEvent() {
  while (gameRunning) {
    char c = std::toupper(Share::ScanKeyboard());
    if (c == '\033') {
      musicPlaying = false;
      gameRunning  = false;
      return;
    } else if (!isspace(c)) {
      CheckHit(c);
    }
  }
  return;
}

void CalcScore() {
  if (aCount + bCount == totalNotesCount) { puts(PERFECT_TEXT("ALL COMBO!")); }
  printf(
      "===== Results =====\n"
      BOLD(PERFECT_TEXT("%s") " %dx") RST "\n"
      "Score: %d\n"
      PERFECT_TEXT("PERFECT") ": %d\n"
      GOOD_TEXT("GOOD") ": %d\n"
      MISS_TEXT("MISS") ": %d\n"
      "MaxCombo: %d\n"
      "HitRate: %.2f%%\n",
      title.c_str(),
      speed,
      score,
      aCount,
      bCount,
      fCount,
      maxCombo,
      double(aCount + bCount) / totalNotesCount * 100);
  return;
}

void SelectSpectrum() {
  // 获取所有文件名
  std::vector<std::string> fileNames;
  struct dirent**          namelist;
  int n = scandir("./Spectrums", &namelist, NULL, alphasort);
  if (n < 0) {
    fprintf(stderr, "scandir\n");
    exit(EXIT_FAILURE);
  } else {
    for (int i = 0; i < n; ++i) {
      if (namelist[i]->d_type == 8) {
        fileNames.push_back(namelist[i]->d_name);
      }
      free(namelist[i]);
    }
    free(namelist);
  }

  // 等待用户选择一个文件
  int selected = 0, c = 0;
  do {
    Share::Clear();
    puts("===== Select a file (" PERFECT_TEXT("J") "/" PERFECT_TEXT(
        "K") "/" GOOD_TEXT("Enter") "/" MISS_TEXT("ESC") ") =====" RST);
    for (int i = 0; i < (int) fileNames.size(); ++i) {
      if (i == selected) {
        puts(INVEX(fileNames[i]).c_str());
      } else {
        puts(fileNames[i].c_str());
      }
    }
    c = std::toupper(Share::ScanKeyboard());
    if (c == 'J') {
      selected++;
      if (selected >= (int) fileNames.size()) {
        selected = fileNames.size() - 1;
      }
    } else if (c == 'K') {
      selected--;
      if (selected < 0) { selected = 0; }
    } else if (c == '\033') {
      exit(0);
    }
  } while (c != '\n');
  memset(spectrum, EMPTY, sizeof(spectrum));
  title = fileNames[selected];
  title.pop_back();
  title.pop_back();
  title.pop_back();
  std::string selectedFileName = fileNames[selected];
  LoadSpectrum(selectedFileName);
  soundName   = "./Sounds/" + title + ".ogg";
  gameRunning = true;
  return;
}

void SelectSpeed() {
  std::vector<int> speeds   = { 1, 2, 4, 8 };
  int              selected = 0;
  char             c;
  do {
    Share::Clear();
    puts("===== Select a speed (" PERFECT_TEXT("J") "/" PERFECT_TEXT(
        "K") "/" GOOD_TEXT("Enter") "/" MISS_TEXT("ESC") ") =====" RST);
    for (int i = 0; i < (int) speeds.size(); ++i) {
      if (i == selected) {
        printf(INVE("%dx\n"), speeds[i]);
      } else {
        printf("%dx\n", speeds[i]);
      }
    }
    c = std::toupper(Share::ScanKeyboard());
    if (c == 'J') {
      selected++;
      if (selected >= (int) speeds.size()) { selected = speeds.size() - 1; }
    } else if (c == 'K') {
      selected--;
      if (selected < 0) { selected = 0; }
    } else if (c == '\033') {
      exit(0);
    }
  } while (c != '\n');
  speed = speeds[selected];
  return;
}

int main(const int argsLength, const char* args[]) {
  srand(time(NULL));
  InitSDL();
  SelectSpeed();
  SelectSpectrum();
  std::thread kbeThread = std::thread(KeyboardEvent);
  std::thread bgmThread = std::thread(PlayBGM, soundName, bgmDelay);
  while (gameRunning) {
    UpdateSpectrum();
    UpdateScreen();
    CheckFell();
    std::this_thread::sleep_for(std::chrono::milliseconds(delay / speed));
  }
  kbeThread.detach();
  bgmThread.join(); // 等待释放内存
  Share::Clear();
  CalcScore();
  return 0;
}
